package cn.likole.oj.module;


import cn.likole.oj.bean.CustomInput;
import cn.likole.oj.bean.Solution;
import cn.likole.oj.bean.SourceCode;
import cn.likole.oj.bean.User;
import cn.likole.oj.service.ProblemService;
import org.apache.commons.io.FileUtils;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.QueryResult;
import org.nutz.dao.Sqls;
import org.nutz.dao.pager.Pager;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.ViewModel;
import org.nutz.mvc.annotation.*;
import org.nutz.mvc.filter.CheckSession;
import org.nutz.plugins.apidoc.annotation.Api;
import org.nutz.plugins.apidoc.annotation.ReturnKey;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static cn.likole.oj.constant.Constant.*;


/**
 * Created by likole on 7/28/18.
 */
@IocBean
@At("/solution")
@Filters(@By(type = CheckSession.class, args = {"uid", "/"}))
@Api(name = "解题模块",description = "跟解题相关的模块")
public class SolutionModule {

    @Inject
    private Dao dao;

    @Inject
    private ProblemService problemService;

    @At
    @Api(name = "获取源码",description = "只能获取用户自己的源码")
    public Object sourceCode(@Param("sid")int id, HttpSession session){
        NutMap re=new NutMap();

        Solution solution=dao.fetch(Solution.class,id);
        if(solution==null) return re.setv("ok",false).setv("msg","该solution不存在");
        if(solution.getUserId()!= (int)session.getAttribute("uid")) return re.setv("ok",false).setv("msg","只能查看自己的代码");
        dao.fetchLinks(solution,"sourceCode");
        return re.setv("ok",true).setv("code",solution.getSourceCode()).setv("language",solution.getLanguage());
    }


    @At
    @Api(name = "上一次提交的代码",description = "获取指定问题指定语言自己上一次提交的代码")
    public Object lastCode(@Param("pid") int pid,@Param("language") int language,HttpSession session){
        NutMap re=new NutMap();

        Cnd cnd=Cnd.where("userId","=",session.getAttribute("uid")).and("problemId","=",pid).and("language","=",language);
        cnd.getOrderBy().desc("solutionId");
        cnd.limit(1,1);
        List<Solution> solutions=dao.query(Solution.class,cnd);
        if(solutions.size()>0){
            dao.fetchLinks(solutions.get(0),"sourceCode");
            return re.setv("ok",true).setv("code",solutions.get(0).getSourceCode());
        }
        return re.setv("ok",false);
    }

    @At
    @Api(name = "上一次提交的语言",description = "返回该用户最后一次提交所使用的语言")
    public Object lastLanguage(HttpSession session){
        return new NutMap().setv("ok",true).setv("language",dao.fetch(User.class,(int)session.getAttribute("uid")).getLanguage()) ;
    }

    @At
    @Api(name = "可用语言",description = "获取用户可以使用的语言列表")
    public List<Integer> availableLanguage(){
        List<Integer> list=new ArrayList<>();
        for(int i=0;i<LANGUAGE_EXT.length;i++){
            if(((~LANGUAGE_MASK)&(1<<i))!=0) list.add(i);
        }
        return list;
    }


    @At
    @Api(name = "获取代码模板",description = "可能有一些题目只是补充代码,所以要使用代码模板")
    public Object template(@Param("pid")int pid,@Param("language")int language) throws IOException {
        NutMap re=new NutMap();

        File file=new File( Thread.currentThread().getContextClassLoader().getResource("/").getPath()+DATA_PATH+pid+"/template"+LANGUAGE_EXT[language]);
        if(file.exists()){
            return re.setv("ok",true).setv("content",FileUtils.readFileToString(file));
        }
        return re.setv("ok",false);
    }


    @At("/submit")
    @Api(name = "提交代码",description = "需要 code,language,problemId.\n如果要测试运行,题号传负数,外加input即可",
    ok={@ReturnKey(key = "ok"),@ReturnKey(key = "sid"),@ReturnKey(key="codeLength")})
    @POST
    public Object submitCode(@Param("code") String code, @Param("..")Solution solution, @Param("input")String input, HttpServletRequest request, HttpSession session) throws IOException {
        NutMap re=new NutMap();

        //判断问题是否可用
        if(solution.getProblemId()==0) return re.setv("ok",false).setv("msg","该问题不存在");
        boolean test_run=false;//是否为测试运行
        if(solution.getProblemId()<0){
            test_run=true;
            solution.setProblemId(-solution.getProblemId());
        }
        User user= dao.fetch(User.class,(int)session.getAttribute("uid"));
        if(user.getStage()==0){
            //第一阶段
            if(problemService.availableInTutorial(solution.getProblemId(),user)==null)
                return re.setv("ok",false).setv("msg","该用户目前所在章节无此题目");
        }else {
            //第二阶段
            //todo:第二阶段判断题目是否可以提交
        }

        //代码语言
        if(solution.getLanguage()>=LANGUAGE_NAME.length||solution.getLanguage()<0) solution.setLanguage(0);
        if(((~LANGUAGE_MASK)&(1<<solution.getLanguage()))==0) return re.setv("ok",false).setv("msg","该语言暂未启用");
        user.setLanguage(solution.getLanguage());
        dao.update(user,"^language$");

        //判断提交频率
        int num= dao.func(Solution.class,"count","solutionId", Cnd.where("userId","=",(int)session.getAttribute("uid")).and(new Static("inDate>SUBDATE(now(),interval 60 second)")));
        if(num>=6) return re.setv("ok",false).setv("msg","提交频率过高，请稍后重试");

        //prepend
        File prepend=new File(Thread.currentThread().getContextClassLoader().getResource("/").getPath()+DATA_PATH+solution.getProblemId()+"/prepend."+LANGUAGE_EXT[solution.getLanguage()]);
        if(prepend.exists()){
            code= FileUtils.readFileToString(prepend)+code;
        }

        //append
        File append=new File(Thread.currentThread().getContextClassLoader().getResource("/").getPath()+DATA_PATH+solution.getProblemId()+"/append."+LANGUAGE_EXT[solution.getLanguage()]);
        if(prepend.exists()){
            code= code+FileUtils.readFileToString(prepend);
        }

        //python
        if(solution.getLanguage()==6){
            code="# coding=utf-8\n"+code;
        }

        //代码长度
        solution.setCodeLength(code.length());
        if(code.length()<2) return re.setv("ok",false).setv("msg","代码过短");
        if(code.length()>65536) return re.setv("ok",false).setv("msg","代码过长");

        //设置必要的信息
        if(test_run)solution.setProblemId(0);
        solution.setUserId((int)session.getAttribute("uid"));
        solution.setInDate(new Date());
        solution.setIp(request.getRemoteAddr());
        SourceCode sourceCode=new SourceCode();
        sourceCode.setSource(code);
        solution.setSourceCode(sourceCode);
        dao.insertWith(solution,"sourceCode");
        if(test_run){//测试运行
            CustomInput customInput=new CustomInput();
            customInput.setSolutionId(solution.getSolutionId());
            customInput.setText(input);
            dao.insert(customInput);
        }
        return re.setv("ok", true).setv("sid",solution.getSolutionId()).setv("codeLength",solution.getCodeLength());
    }


    @At("/status")
    @Ok("json:{locked:'ip|sourceCode|compileInfo|runtimeInfo|contestId|contestNum'}")
    @Api(name="获取状态列表",description = "如要获取全部结果,language和result参数都需要设置为-1")
    public Object statusList(@Param("pid")int pid, @Param("username") String username,@Param("language") int language,@Param("result") int result, @Param("..")Pager pager){
        Cnd cnd=Cnd.where("1","=","1");
        if(pid!=0) cnd.and("problemId","=",pid);
        if(!Strings.isBlank(username)){
            List<User> users=dao.query(User.class,Cnd.where("nickname","=",username));
            if(users.size()>0)
                cnd.and("userId","=",users.get(0).getUserId());
        }
        if(language!=-1) cnd.and("language","=",language);
        if(result!=-1) cnd.and("result","=",result);
        cnd.getOrderBy().desc("solutionId");

        QueryResult qr = new QueryResult();
        qr.setList(dao.query(Solution.class, cnd, pager));
        pager.setRecordCount(dao.count(Solution.class, cnd));
        qr.setPager(pager);
        return qr;
    }

    @At
    @Api(name = "分析solution",description = "分析用户每种结果的数量以及每个时间点提交的数量",
            ok={@ReturnKey(key = "result" ,description = "每种结果的数量"),
            @ReturnKey(key = "submit",description = "提交时间的分布,时间为精确到毫秒时间戳,一天一个数值"),
            @ReturnKey(key="ac",description = "ac时间的分布")})
    public Object analyzeResult(@Param("uid")int uid,HttpSession session){
        NutMap re=new NutMap();

        if(uid==0) uid=(int)session.getAttribute("uid");
        User user= dao.fetch(User.class,uid);
        if(user==null) return re.setv("ok",false).setv("msg","该用户不存在");

        Sql sql= Sqls.create("select result,count(1) as num from solution where userId=@uid group by result");
        sql.params().set("uid",user.getUserId());
        sql.setCallback(Sqls.callback.records());
        dao.execute(sql);

        Sql sql1= Sqls.create("SELECT UNIX_TIMESTAMP(date(inDate))*1000 md,count(1) num FROM `solution` where  `userId`=@uid  group by md order by md desc");
        sql1.params().set("uid",user.getUserId());
        sql1.setCallback(Sqls.callback.records());
        dao.execute(sql1);

        Sql sql2= Sqls.create("SELECT UNIX_TIMESTAMP(date(inDate))*1000 md,count(1) num FROM `solution` where `userId`=@uid and result=4 group by md order by md desc");
        sql2.params().set("uid",user.getUserId());
        sql2.setCallback(Sqls.callback.records());
        dao.execute(sql2);

        return re.setv("ok",true).setv("result",sql.getResult()).setv("submit",sql1.getResult()).setv("ac",sql2.getResult());
    }


//    @At
//    @Api(name = "分析solution",description = "分析用户每种结果的数量以及每个时间点提交的数量")
//    public Object analyzeResult2(HttpSession session){
//        NutMap re=new NutMap();
//
//        User user= dao.fetch(User.class,(int)session.getAttribute("uid"));
//
//        Sql sql= Sqls.create("select result,count(1) as num from solution where userId=@uid group by result");
//        sql.params().set("uid",user.getUserId());
//        sql.setCallback(Sqls.callback.records());
//        dao.execute(sql);
//        List<String> resultx=new ArrayList<>();
//        List<Integer> resulty=new ArrayList<>();
//        for(Record record:(List<Record>)sql.getResult()){
//            resultx.add(JUDGE_RESULT[record.getInt("result")]);
//            resulty.add(record.getInt("num"));
//        }
//
//        Sql sql1= Sqls.create("SELECT UNIX_TIMESTAMP(date(inDate))*1000 md,count(1) num FROM `solution` where  `userId`=@uid  group by md order by md desc");
//        sql1.params().set("uid",user.getUserId());
//        sql1.setCallback(Sqls.callback.records());
//        dao.execute(sql1);
//        List<Long> submitx=new ArrayList<>();
//        List<Integer> submity=new ArrayList<>();
//        for(Record record:(List<Record>)sql1.getResult()){
//            submitx.add(record.getLong("md"));
//            submity.add(record.getInt("num"));
//        }
//
//        Sql sql2= Sqls.create("SELECT UNIX_TIMESTAMP(date(inDate))*1000 md,count(1) num FROM `solution` where `userId`=@uid and result=4 group by md order by md desc");
//        sql2.params().set("uid",user.getUserId());
//        sql2.setCallback(Sqls.callback.records());
//        dao.execute(sql2);
//        List<Long> acx=new ArrayList<>();
//        List<Integer> acy=new ArrayList<>();
//        for(Record record:(List<Record>)sql2.getResult()){
//            acx.add(record.getLong("md"));
//            acy.add(record.getInt("num"));
//        }
//
//        return re.setv("ok",true).setv("resultx",resultx).setv("resulty",resulty).setv("submitx",submitx).setv("submity",submity).setv("acx",acx).setv("acy",acy);
//    }

    @At
    @Api(name = "获取错误信息",description = "获取编译错误,运行错误,答案错误,格式错误,运行完成的详细信息,对应result为11,10,6,5,13")
    public Object errorInfo(@Param("sid")int sid,HttpSession session){
        NutMap re=new NutMap();

        Solution solution=dao.fetch(Solution.class,sid);
        if(solution==null) return re.setv("ok",false).setv("msg","该solution不存在");
        if(solution.getUserId()!=(int) session.getAttribute("uid")) return re.setv("ok",false).setv("msg","没有权限查看该信息");
        if(solution.getResult()==11){//编译错误
            dao.fetchLinks(solution,"compileInfo");
            if(solution.getCompileInfo()==null) return re.setv("ok",false).setv("msg","该solution无编译错误信息");
            return re.setv("ok",true).setv("result",11).setv("error",solution.getCompileInfo());
        }else if(solution.getResult()==10||solution.getResult()==13){//编译错误,运行完成
            dao.fetchLinks(solution,"runtimeInfo");
            if(solution.getRuntimeInfo()==null) return re.setv("ok",false).setv("msg","该solution无运行信息");
            return re.setv("ok",true).setv("result",solution.getResult()).setv("error",solution.getRuntimeInfo());
        }else if(solution.getResult()==05||solution.getResult()==6){//编译错误,运行错误
            //todo:商城
            return re.setv("ok",false).setv("msg","请购买");
        }
        return re.setv("ok",false).setv("msg","该solution无相关信息");
    }

    @At
    @Api(name = "结果回调",description = "获取问题的结果,如果为测试运行,test设为1")
    public Object ResultCallback(@Param("sid")int sid,@Param("test")int test,HttpSession session){
        NutMap re=new NutMap();

        Solution solution=dao.fetch(Solution.class,sid);
        if(solution==null) return re.setv("ok",false).setv("msg","该solution不存在");
        if(solution.getUserId()!=(int)session.getAttribute("uid")) return re.setv("ok",false).setv("msg","没有权限查看该信息");

        if(test==1){//测试运行
            if(solution.getResult()==11){//编译错误
                dao.fetchLinks(solution,"compileInfo");
            }else{
                dao.fetchLinks(solution,"runtimeInfo");
            }

            if(solution.getCompileInfo()==null&&solution.getRuntimeInfo()==null) return re.setv("ok",false).setv("msg","请稍后获取结果");
            dao.delete(CustomInput.class,sid);
            if(solution.getRuntimeInfo()==null){
                return re.setv("ok",true).setv("result",11).setv("info",solution.getCompileInfo().getError());
            }else{
                return re.setv("ok",true).setv("result",solution.getResult()).setv("info",solution.getRuntimeInfo().getError());
            }
        }else{
            return re.setv("ok",true).setv("result",solution.getResult()).setv("memory",solution.getMemory())
                    .setv("time",solution.getTime()).setv("judger",solution.getJudger());
        }


    }

    @At
    @Ok("re")
    public String submitPage(@Param("pid") int pid, ViewModel model){
        if(pid==0) return ">>:/";
            //todo:判断问题是否可用
        model.setnx("pid",pid);
        return "jsp:jsp.submit";
    }

    @At
    @Ok("jsp:jsp.status")
    public void status(){
    }

}
